#!/usr/bin/perl -w

## Copyright John Duprey (2004)
## NOTE: I borrowed the safe_read_from_pipe() and read_from_process() 
## 	functions with some modification from the subversion 
##	repo-tools/hook-scripts directory.

use strict;
use DBI;

# MySQL DB information
my $host = 'localhost';
my $db = 'svncommits';
my $db_user = 'user';
my $db_password = 'password';

# Svnlook path.
my $tmp_dir = '/tmp';
my $svnlook = "/usr/bin/svnlook";

my($repos, $rev) = @ARGV;

#############
## EXECUTE ##
my $dbh = open_dbi( $host, $db, $db_user, $db_password );

commit2db( $repos, $rev, 0 );
$dbh->disconnect or warn "Disconnection failed: $DBI::errstr\n";
exit;
#############

#############################
## Functions

sub commit2db
{
   my($repos, $rev, $quiet) = @_;

   if( 1 != $quiet )
   {
	   print("Repos:$repos, rev:$rev\n");
   }

   if( $rev <= 0 )
   {
	   my @youngestRevLines = 
		   &read_from_process($svnlook, 'youngest', $repos);
	   my $latestRev = shift @youngestRevLines;

           if( $latestRev =~ /failed with this output/ )
           {
              die( "Failed to find the latest revision for $repos because: " .
              " $latestRev", @youngestRevLines );
           }

	   print("Rebuilding commit log info for $repos revisions 1 - $latestRev.\n");
	   for( my $i = 1; $i <= $latestRev; $i++ )
	   {
		   print(".");
		   commit2db( $repos, $i, 1 );
	   }
	   print( "\n$latestRev revisions logged. Done.\n" );
	   return(0);
   }

   ######################################################################
   # Harvest data using svnlook.
   
   # Change into /tmp so that svnlook diff can create its .svnlook
   # directory.
   chdir($tmp_dir) or die "$0: cannot chdir `$tmp_dir': $!\n";

   # Get the author, date, and log from svnlook.
   my @svnlooklines = &read_from_process($svnlook, 'info', $repos, '-r', $rev);

   my $author = shift @svnlooklines;
   if( $author =~ /failed with this output/ )
   {
      warn( "Failed to get information about revision $rev because:\n\t"
            . $author . " ", @svnlooklines );
      return(1);
   }
   if( 0 == length($author) )
   {
      warn( "Missing user/author for revision $rev, using \"svn_anon\"" );
      $author = "svn_anon";
   }
   if( 254 < length($author) )
   {
      warn( "\"$author\" user/author truncated to the first 254 chars for revision $rev." );
      $author = substr( $author, 0, 254 );
   }

   my $date = shift @svnlooklines;
   shift @svnlooklines;
   my @log = map { "$_\n" } @svnlooklines;

   
   # Figure out what directories have changed using svnlook.
   my @dirschanged = &read_from_process($svnlook, 'dirs-changed', $repos, 
                                        '-r', $rev);
   # Lose the trailing slash in the directory names if one exists, except
   # in the case of '/'.
   my $rootchanged = 0;
   for (my $i=0; $i<@dirschanged; ++$i)
   {
     if ($dirschanged[$i] eq '/')
     {
        $rootchanged = 1;
     }
     else
     {
        $dirschanged[$i] =~ s#^(.+)[/\\]$#$1#;
     }
   }
   
   # Figure out what files have changed using svnlook.
   @svnlooklines = &read_from_process($svnlook, 'changed', $repos, '-r', $rev);

   my $textLog = "@log";
   my $insertID = -1;
   my $repID = -1;
   my $authorID = -1;
   ($insertID, $repID, $authorID) = 
	   insertRevisionIntoDB( $dbh, $rev, $repos, $author, $date, $textLog );
   if( $insertID == -1 )
   {
	   print "Failed to insert the revision into the DB.\n";
	   return( 1 );
   }
   
   
   # Parse the changed nodes.
   my @adds;
   my @dels;
   my @mods;
   foreach my $line (@svnlooklines)
   {
       my $path = '';
       my $code = '';
   
       # Split the line up into the modification code and path, ignoring
       # property modifications.
       if ($line =~ /^(.).  (.*)$/)
       {
          $code = $1;
          $path = $2;
       }
   
       if ($code eq 'A')
       {
         push(@adds, $path);
       }
       elsif ($code eq 'D')
       {
         push(@dels, $path);
       }
       else
       {
           push(@mods, $path);
       }
   }

   foreach my $item (@adds)
   {
	  insertResourceIntoDB($dbh, $insertID, $repID, $item, 'A' );
   }

   foreach my $item (@mods)
   {
	  insertResourceIntoDB($dbh, $insertID, $repID, $item, 'M' );
   }

   foreach my $item (@dels)
	{
		insertResourceIntoDB($dbh, $insertID, $repID, $item, 'D');
	}
}


sub insertRevisionIntoDB
{
    my( $dbh, $rev, $repository, $author, $date, $log ) = @_;
##	print( "$rev, $repository, $author, $date, $log\n" );

	my $sth;
	my $rep_id = -1;
	my $author_id = -1;

	############################################################
	## Does the repository exist in the respository table?
	my $queryString = "SELECT id FROM repository where repository=" . $dbh->quote($repository);
	$rep_id = $dbh->selectrow_array( $queryString );
    if( !defined( $rep_id ) ) ## The repository doesn't already exist
	{
		my $revsth = $dbh->prepare( 
			q{
				INSERT INTO repository( repository, alias, owner )
					VALUES( ?, ?, ? )
			}) || die $dbh->errstr;
		$revsth->execute($repository, $repository, $author_id) || die $dbh->errstr;

		$rep_id = $dbh->selectrow_array( $queryString );	
		if( !defined($rep_id) ) ## The repository doesn't already exist
		{
			print( "Failed to insert repository: $repository.\n" );
			exit( 1 );
		}
		print( "New repository $repository with id $rep_id.\n" );
		$revsth->finish();
	}
	###############################################################

	###############################################################
	## Does the author exist in the author table
	$queryString = "SELECT id FROM author where user=" . $dbh->quote($author) . 
		" AND rep_id=" . $dbh->quote($rep_id);
	$author_id = $dbh->selectrow_array( $queryString );	
	if( !defined($author_id) ) ## The author doesn't already exist
	{
		my $authorsth = $dbh->prepare( 
			q{
				INSERT INTO author( rep_id, user, full_name  )
					VALUES( ?, ?, ? )
			}) || die $dbh->errstr;
		$authorsth->execute($rep_id, $author, $author);

		$author_id = $dbh->selectrow_array( $queryString );	
		if( !defined($author_id) ) ## The repository doesn't already exist
		{
			print( "Failed to insert new author: $author.\n" );
			exit(1);
		}
		print( "Created new author $author with id $author_id.\n" );
		$authorsth->finish();
	}
	###############################################################

	###############################################################
	## Insert a new revision entry
	$sth = $dbh->prepare( 
	q{
		INSERT INTO revision(  rev, rep_id, author_id, date, log ) 
								VALUES (?, ?, ?, ?, ?)
	}) || die $dbh->errstr;
	$sth->execute($rev, $rep_id, $author_id, $date, $log) || warn $dbh->errstr;
	###############################################################

	## Autocommit is on and this will produce an error emssage..
	##	$dbh->commit || die $dbh->errstr;

	###############################################################
	## Get the revision id
	$sth = $dbh->prepare(
		q{ 
			SELECT id FROM revision WHERE rev = ? AND rep_id = ? 
		}) || die $dbh->errstr;
	$sth->execute($rev, $rep_id) || die $dbh->errstr;
	my $id = -1;
	$id = $sth->fetchrow_array;

	## Return the revsion id...
    return( ($id, $rep_id, $author_id ) );
}


sub insertResourceIntoDB
{
	my( $dbh, $rev_id, $rep_id, $resource, $action ) = @_;

##	print( "$rev_id, $resource, $action\n" );

	my $sth = $dbh->prepare(
	q{
		INSERT INTO resource( rev_id, rep_id, resource, action )
								VALUES (?, ?, ?, ?)
	}) || die $dbh->errstr;
	
	$sth->execute($rev_id, $rep_id, $resource, $action) || warn $dbh->errstr;

}


sub open_dbi
{
   # Declare and initialize variables
    my( $host, $db, $db_user, $db_password ) = @_;

   # Connect to the requested server
   my $dbh = DBI->connect(
			  "dbi:mysql:$db:$host",
			  "$db_user",
			  "$db_password",
			  { 
			      RaiseError => 0,
			      PrintError => 0}
			  ) or err_trap("Cannot connect to the database");
   return $dbh;
}#end: open_dbi



############################################################
############################################################
############################################################
############################################################
## These functions were taken from the subversion example/contrib area

# Start a child process safely without using /bin/sh.
sub safe_read_from_pipe
{
  unless (@_)
    {
      print( "$0: safe_read_from_pipe passed no arguments.\n" );
    }

  my $pid = open(SAFE_READ, '-|');
  unless (defined $pid)
    {
      die "$0: cannot fork: $!\n";
    }
  unless ($pid)
    {
      open(STDERR, ">&STDOUT")
        or die "$0: cannot dup STDOUT: $!\n";
      exec(@_)
        or die "$0: cannot exec `@_': $!\n";
    }
  my @output;
  while (<SAFE_READ>)
    {
      s/[\r\n]+$//;
      push(@output, $_);
    }
  close(SAFE_READ);
  my $result = $?;
  my $exit   = $result >> 8;
  my $signal = $result & 127;
  my $cd     = $result & 128 ? "with core dump" : "";
  if ($signal or $cd)
    {
      warn "$0: pipe from `@_' failed $cd: exit=$exit signal=$signal\n";
    }
  if (wantarray)
    {
      return ($result, @output);
    }
  else
    {
      return $result;
    }
}

# Use safe_read_from_pipe to start a child process safely and return
# the output if it succeeded or an error message followed by the output
# if it failed.
sub read_from_process
{
  unless (@_)
    {
      print( "$0: read_from_process passed no arguments.\n" );
    }
  my ($status, @output) = &safe_read_from_pipe(@_);
  if ($status)
    {
      return ("$0: `@_' failed with this output:", @output);
    }
  else
    {
      return @output;
    }
}

